﻿// Learn more about F# at http://fsharp.net

module XSerialization

open System.Reflection
open System.Xml
open System.Xml.Serialization
open System.IO
open System
open System.Collections.Generic
open System.Collections

// Reflection helpers

let props obj = obj.GetType().GetProperties()

//let (|Attr|_|) at (p : PropertyInfo) = 
//    let attributes = p.GetCustomAttributes(at, true)
//    if attributes.Length = 1 then Some(attributes.[0]) else None

let propertyName (p : PropertyInfo) = 
    let attributes = p.GetCustomAttributes(typeof<XmlElementAttribute>, true) |> Array.map (fun i -> i :?> XmlElementAttribute)
    if attributes.Length = 1 then attributes.[0].ElementName else p.Name

let rec objName (t : Type) = 
    if t.IsGenericType then getGenericTypeName t else t.Name
and getGenericTypeName (t : Type) = 
    let simpleName = t.Name.Substring(0, t.Name.IndexOf('`'))
    simpleName
//    t.GetGenericArguments() |> Array.map objName |> Array.map (fun s -> "_" + s) 
//        |> Array.fold (fun a s -> a + s) simpleName
    
let getValue obj (p : PropertyInfo) = p.GetValue(obj, Array.empty)

let (|Null|_|) o = if o = null then Some() else None

let (|ValueType|_|) o = if o.GetType().IsValueType then Some(o.ToString()) else None

let (|StringType|_|) o = if o.GetType() = typeof<string> then Some(string o) else None

let (|EnumerableType|_|) (o : obj) =
    match o with
    | :? IEnumerable as en -> Some(en) 
    | _ -> None

let (|SimpleType|_|) = function
    | StringType v -> Some(v)
    | ValueType v -> Some(v)
    | _ -> None

let rec doSerialize (writer : XmlWriter) container data = 
    writer.WriteStartElement(container)

    let callForProperty p = doSerialize writer (propertyName p) (getValue data p)

    match data with
    | Null -> ()
    | SimpleType v -> writer.WriteString(v)
    | EnumerableType c -> for o in c do doSerialize writer (objName (o.GetType())) o
    | _ -> props data |> Array.iter callForProperty

    writer.WriteEndElement();


let serialize writer data = doSerialize writer (objName (data.GetType())) data
